/**
 * @file missiles.h
 *
 * Interface of missile functionality.
 */
#pragma once

#include <cstdint>
#include <list>
#include <optional>

#include "engine/displacement.hpp"
#include "engine/point.hpp"
#include "engine/world_tile.hpp"
#include "misdat.h"
#include "monster.h"
#include "player.h"
#include "spelldat.h"
#include "utils/is_of.hpp"

namespace devilution {

constexpr WorldTilePosition GolemHoldingCell = Point { 1, 0 };

struct MissilePosition {
	/** Sprite's pixel offset from tile. */
	Displacement offset;
	/** Pixel velocity while moving */
	Displacement velocity;
	/** Pixels traveled as a numerator of 65,536. */
	Displacement traveled;

	WorldTilePosition tile;
	/** Start position */
	WorldTilePosition start;

	/**
	 * @brief Specifies the location (tile) while rendering
	 */
	WorldTilePosition tileForRendering;
	/**
	 * @brief Specifies the location (offset) while rendering
	 */
	Displacement offsetForRendering;

	/**
	 * @brief Stops the missile (set velocity to zero and set offset to last renderer location; shouldn't matter because the missile doesn't move anymore)
	 */
	void StopMissile()
	{
		velocity = {};
		if (tileForRendering == tile)
			offset = offsetForRendering;
	}
};

enum class MissileSource : uint8_t {
	Player,
	Monster,
	Trap,
};

enum class GuardianFrame : uint8_t {
	Start = 0,
	Idle = 1,
	Attack = 2,
};

enum class AcidPuddleFrame : uint8_t {
	Idle = 0,
	End = 1,
};

enum class FireWallFrame : uint8_t {
	Start = 0,
	Idle = 1,
};

enum class PortalFrame : uint8_t {
	Start = 0,
	Idle = 1,
};

enum class RedPortalFrame : uint8_t {
	Start = 0,
	Idle = 1,
};

struct Missile {
	/** Type of projectile */
	MissileID _mitype;
	MissilePosition position;

private:
	int _mimfnum; // The direction of the missile (direction enum)

public:
	int _mispllvl;
	bool _miDelFlag; // Indicate whether the missile should be deleted
	MissileGraphicID _miAnimType;
	MissileGraphicsFlags _miAnimFlags;
	OptionalClxSpriteList _miAnimData;
	int _miAnimDelay; // Tick length of each frame in the current animation
	int _miAnimLen;   // Number of frames in current animation

	// TODO: This field is no longer used and is always equal to
	// (*_miAnimData)[0].width()
	uint16_t _miAnimWidth;

	int16_t _miAnimWidth2;
	int _miAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
	int _miAnimAdd;
	int _miAnimFrame; // Current frame of animation + 1.
	bool _miDrawFlag;
	bool _miLightFlag;
	bool _miPreFlag;
	uint32_t _miUniqTrans;
	/** @brief Time to live for the missile in game ticks; once 0, the missile will be marked for deletion via _miDelFlag */
	int duration;
	int _misource;
	mienemy_type _micaster;
	int _midam;
	bool _miHitFlag;
	int _midist; // Used for arrows to measure distance travelled (increases by 1 each game tick). Higher value is a penalty for accuracy calculation when hitting enemy
	int _mlid;
	int _mirnd;
	int var1;
	int var2;
	int var3;
	int var4;
	int var5;
	int var6;
	int var7;
	bool limitReached;
	/**
	 * @brief For moving missiles lastCollisionTargetHash contains the last entity (player or monster) that was checked in CheckMissileCol (needed to avoid multiple hits for a entity at the same tile).
	 */
	int16_t lastCollisionTargetHash;

	/** @brief Was the missile generated by a trap? */
	[[nodiscard]] bool IsTrap() const
	{
		return _misource == -1;
	}

	[[nodiscard]] Player *sourcePlayer()
	{
		if (IsNoneOf(_micaster, TARGET_BOTH, TARGET_MONSTERS) || _misource == -1)
			return nullptr;
		return &Players[_misource];
	}

	[[nodiscard]] Monster *sourceMonster()
	{
		if (_micaster != TARGET_PLAYERS || _misource == -1)
			return nullptr;
		return &Monsters[_misource];
	}

	[[nodiscard]] bool isSameSource(Missile &missile)
	{
		return sourceType() == missile.sourceType() && _misource == missile._misource;
	}

	MissileSource sourceType()
	{
		if (_misource == -1)
			return MissileSource::Trap;
		if (_micaster == TARGET_PLAYERS)
			return MissileSource::Monster;
		return MissileSource::Player;
	}

	void setAnimation(MissileGraphicID animtype);

	/**
	 * @brief Sets the missile sprite to the given sheet frame
	 * @param dir Sprite frame
	 */
	void setFrameGroupRaw(int frameGroup)
	{
		_mimfnum = frameGroup;
		setAnimation(_miAnimType);
	}

	void setDefaultFrameGroup()
	{
		setFrameGroupRaw(0);
	}

	template <typename FrameEnum>
	void setFrameGroup(FrameEnum frameGroup)
	{
		setFrameGroupRaw(static_cast<int>(frameGroup));
	}

	/**
	 * @brief Sets the sprite for this missile so it matches the given Direction
	 * @param dir Desired facing
	 */
	void setDirection(Direction dir)
	{
		setFrameGroupRaw(static_cast<int>(dir));
	}

	/**
	 * @brief Sets the sprite for this missile so it matches the given Direction16
	 * @param dir Desired facing at a 22.8125 degree resolution
	 */
	void setDirection(Direction16 dir)
	{
		setFrameGroupRaw(static_cast<int>(dir));
	}

	int getFrameGroupRaw() const
	{
		return _mimfnum;
	}

	template <typename FrameEnum>
	FrameEnum getFrameGroup() const
	{
		static_assert(std::is_enum_v<FrameEnum>, "Frame group must be an enum");
		return static_cast<FrameEnum>(_mimfnum);
	}

	[[nodiscard]] Direction getDirection() const
	{
		return static_cast<Direction>(_mimfnum);
	}

	[[nodiscard]] Direction16 getDirection16() const
	{
		return static_cast<Direction16>(_mimfnum);
	}
};

extern std::list<Missile> Missiles;
extern bool MissilePreFlag;

struct DamageRange {
	int min;
	int max;
};
DamageRange GetDamageAmt(SpellID spell, int spellLevel);

/**
 * @brief Returns the direction a vector from p1(x1, y1) to p2(x2, y2) is pointing to.
 *
 * @code{.unparsed}
 *      W  sW  SW   Sw  S
 *              ^
 *     nW       |       Se
 *              |
 *     NW ------+-----> SE
 *              |
 *     Nw       |       sE
 *              |
 *      N  Ne  NE   nE  E
 * @endcode
 *
 * @param p1 The point from which the vector starts.
 * @param p2 The point from which the vector ends.
 * @return the direction of the p1->p2 vector
 */
Direction16 GetDirection16(Point p1, Point p2);
bool MonsterTrapHit(Monster &monster, int mindam, int maxdam, int dist, MissileID t, DamageType damageType, bool shift);
bool PlayerMHit(Player &player, Monster *monster, int dist, int mind, int maxd, MissileID mtype, DamageType damageType, bool shift, DeathReason deathReason, bool *blocked);

/**
 * @brief Could the missile collide with solid objects? (like walls or closed doors)
 */
bool IsMissileBlockedByTile(Point tile);

void InitMissiles();

struct AddMissileParameter {
	WorldTilePosition dst;
	Direction midir;
	Missile *pParent;
	bool spellFizzled;
};

void AddOpenNest(Missile &missile, AddMissileParameter &parameter);
void AddRuneOfFire(Missile &missile, AddMissileParameter &parameter);
void AddRuneOfLight(Missile &missile, AddMissileParameter &parameter);
void AddRuneOfNova(Missile &missile, AddMissileParameter &parameter);
void AddRuneOfImmolation(Missile &missile, AddMissileParameter &parameter);
void AddRuneOfStone(Missile &missile, AddMissileParameter &parameter);
void AddReflect(Missile &missile, AddMissileParameter &parameter);
void AddBerserk(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: Direction to place the spawn
 */
void AddHorkSpawn(Missile &missile, AddMissileParameter &parameter);
void AddJester(Missile &missile, AddMissileParameter &parameter);
void AddStealPotions(Missile &missile, AddMissileParameter &parameter);
void AddStealMana(Missile &missile, AddMissileParameter &parameter);
void AddSpectralArrow(Missile &missile, AddMissileParameter &parameter);
void AddWarp(Missile &missile, AddMissileParameter &parameter);
void AddLightningWall(Missile &missile, AddMissileParameter &parameter);
void AddBigExplosion(Missile &missile, AddMissileParameter &parameter);
void AddImmolation(Missile &missile, AddMissileParameter &parameter);
void AddLightningBow(Missile &missile, AddMissileParameter &parameter);
void AddMana(Missile &missile, AddMissileParameter &parameter);
void AddMagi(Missile &missile, AddMissileParameter &parameter);
void AddRingOfFire(Missile &missile, AddMissileParameter &parameter);
void AddSearch(Missile &missile, AddMissileParameter &parameter);
void AddChargedBoltBow(Missile &missile, AddMissileParameter &parameter);
void AddElementalArrow(Missile &missile, AddMissileParameter &parameter);
void AddArrow(Missile &missile, AddMissileParameter &parameter);
void AddPhasing(Missile &missile, AddMissileParameter &parameter);
void AddFirebolt(Missile &missile, AddMissileParameter &parameter);
void AddMagmaBall(Missile &missile, AddMissileParameter &parameter);
void AddTeleport(Missile &missile, AddMissileParameter &parameter);
void AddNovaBall(Missile &missile, AddMissileParameter &parameter);
void AddFireWall(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the missile-light
 * var2: Y coordinate of the missile-light
 * var4: X coordinate of the missile-light
 * var5: Y coordinate of the missile-light
 */
void AddFireball(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the missile
 * var2: Y coordinate of the missile
 */
void AddLightningControl(Missile &missile, AddMissileParameter &parameter);
void AddLightning(Missile &missile, AddMissileParameter &parameter);
void AddMissileExplosion(Missile &missile, AddMissileParameter &parameter);
void AddWeaponExplosion(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: Animation
 */
void AddTownPortal(Missile &missile, AddMissileParameter &parameter);
void AddFlashBottom(Missile &missile, AddMissileParameter &parameter);
void AddFlashTop(Missile &missile, AddMissileParameter &parameter);
void AddManaShield(Missile &missile, AddMissileParameter &parameter);
void AddFlameWave(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: Animation
 * var3: Light strength
 */
void AddGuardian(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the destination
 * var2: Y coordinate of the destination
 */
void AddChainLightning(Missile &missile, AddMissileParameter &parameter);
void AddRhino(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the missile-light
 * var2: Y coordinate of the missile-light
 */
void AddGenericMagicMissile(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the missile-light
 * var2: Y coordinate of the missile-light
 */
void AddAcid(Missile &missile, AddMissileParameter &parameter);
void AddAcidPuddle(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: mmode of the monster
 * var2: mnum of the monster
 */
void AddStoneCurse(Missile &missile, AddMissileParameter &parameter);
void AddGolem(Missile &missile, AddMissileParameter &parameter);
void AddApocalypseBoom(Missile &missile, AddMissileParameter &parameter);
void AddHealing(Missile &missile, AddMissileParameter &parameter);
void AddHealOther(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the missile-light
 * var2: Y coordinate of the missile-light
 * var4: X coordinate of the destination
 * var5: Y coordinate of the destination
 */
void AddElemental(Missile &missile, AddMissileParameter &parameter);
void AddIdentify(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the first wave
 * var2: Y coordinate of the first wave
 * var3: Direction of the first wave
 * var4: Direction of the second wave
 * var5: X coordinate of the second wave
 * var6: Y coordinate of the second wave
 */
void AddWallControl(Missile &missile, AddMissileParameter &parameter);
void AddInfravision(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: X coordinate of the destination
 * var2: Y coordinate of the destination
 */
void AddFlameWaveControl(Missile &missile, AddMissileParameter &parameter);
void AddNova(Missile &missile, AddMissileParameter &parameter);
void AddRage(Missile &missile, AddMissileParameter &parameter);
void AddItemRepair(Missile &missile, AddMissileParameter &parameter);
void AddStaffRecharge(Missile &missile, AddMissileParameter &parameter);
void AddTrapDisarm(Missile &missile, AddMissileParameter &parameter);
void AddApocalypse(Missile &missile, AddMissileParameter &parameter);
void AddInferno(Missile &missile, AddMissileParameter &parameter);
void AddInfernoControl(Missile &missile, AddMissileParameter &parameter);

/**
 * var1: Light strength
 * var2: Base direction
 */
void AddChargedBolt(Missile &missile, AddMissileParameter &parameter);
void AddHolyBolt(Missile &missile, AddMissileParameter &parameter);
void AddResurrect(Missile &missile, AddMissileParameter &parameter);
void AddResurrectBeam(Missile &missile, AddMissileParameter &parameter);
void AddTelekinesis(Missile &missile, AddMissileParameter &parameter);
void AddBoneSpirit(Missile &missile, AddMissileParameter &parameter);
void AddRedPortal(Missile &missile, AddMissileParameter &parameter);
void AddDiabloApocalypse(Missile &missile, AddMissileParameter &parameter);
Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype,
    mienemy_type micaster, int id, int midam, int spllvl,
    Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt);
inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype,
    mienemy_type micaster, const Player &player, int midam, int spllvl,
    Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt)
{
	return AddMissile(src, dst, midir, mitype, micaster, player.getId(), midam, spllvl, parent, lSFX);
}
inline Missile *AddMissile(WorldTilePosition src, WorldTilePosition dst, Direction midir, MissileID mitype,
    mienemy_type micaster, const Monster &monster, int midam, int spllvl,
    Missile *parent = nullptr, std::optional<SfxID> lSFX = std::nullopt)
{
	return AddMissile(src, dst, midir, mitype, micaster, static_cast<int>(monster.getId()), midam, spllvl, parent, lSFX);
}
void ProcessElementalArrow(Missile &missile);
void ProcessArrow(Missile &missile);
void ProcessGenericProjectile(Missile &missile);
void ProcessNovaBall(Missile &missilei);
void ProcessAcidPuddle(Missile &missile);
void ProcessFireWall(Missile &missile);
void ProcessFireball(Missile &missile);
void ProcessHorkSpawn(Missile &missile);
void ProcessRune(Missile &missile);
void ProcessLightningWall(Missile &missile);
void ProcessBigExplosion(Missile &missile);
void ProcessLightningBow(Missile &missile);
void ProcessRingOfFire(Missile &missile);
void ProcessSearch(Missile &missile);
void ProcessImmolation(Missile &missile);
void ProcessSpectralArrow(Missile &missile);
void ProcessLightningControl(Missile &missile);
void ProcessLightning(Missile &missile);
void ProcessTownPortal(Missile &missile);
void ProcessFlashBottom(Missile &missile);
void ProcessFlashTop(Missile &missile);
void ProcessFlameWave(Missile &missile);
void ProcessGuardian(Missile &missile);
void ProcessChainLightning(Missile &missile);
void ProcessWeaponExplosion(Missile &missile);
void ProcessMissileExplosion(Missile &missile);
void ProcessAcidSplate(Missile &missile);
void ProcessTeleport(Missile &missile);
void ProcessStoneCurse(Missile &missile);
void ProcessApocalypseBoom(Missile &missile);
void ProcessRhino(Missile &missile);
void ProcessWallControl(Missile &missile);
void ProcessInfravision(Missile &missile);
void ProcessApocalypse(Missile &missile);
void ProcessFlameWaveControl(Missile &missile);
void ProcessNova(Missile &missile);
void ProcessRage(Missile &missile);
void ProcessInferno(Missile &missile);
void ProcessInfernoControl(Missile &missile);
void ProcessChargedBolt(Missile &missile);
void ProcessHolyBolt(Missile &missile);
void ProcessElemental(Missile &missile);
void ProcessBoneSpirit(Missile &missile);
void ProcessResurrectBeam(Missile &missile);
void ProcessRedPortal(Missile &missile);
void ProcessMissiles();
void SetUpMissileAnimationData();
void RedoMissileFlags();

#ifdef BUILD_TESTING
void TestRotateBlockedMissile(Missile &missile);
#endif

} // namespace devilution
